﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using ViewMaker;
using ViewMaker.Core;
using ViewMaker.Core.Controls;
using System.Collections.Generic;
using CloudStorageLight.WpfClient.Properties;
using System.Windows.Threading;
using ViewMaker.Core.Wpf;
using System.Threading;
using System.Text;
using Microsoft.VisualBasic.FileIO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Web;

namespace CloudStorageLight.WpfClient
{
    /// <summary>
    /// ファイルエクスプローラ Main
    /// </summary>
    public class MainVM : AppViewModel
    {
        private int maxServerCall = 5;
        private List<string> history = new List<string>();
        private int histroyIndex = 0;
        private bool stockHistory = true;

        internal string StartPath { get; set; }

        public CreateDirectoryVM CreateDirectoryDialog
        {
            get { return _CreateDirectoryDialog; }
        }
        private CreateDirectoryVM _CreateDirectoryDialog = new CreateDirectoryVM();

        public DirectorySetupVM DirectorySetupDialog
        {
            get { return _DirectorySetup; }
        }
        private DirectorySetupVM _DirectorySetup = new DirectorySetupVM();


        public bool Online
        {
            get { return !BlobDataManager.Instance.Offline; }
        }
        public double FontSize
        {
            get { return (double)new FontSizeConverter().ConvertFrom(SelectedFontSizes); }
        }

        public string[] FontSizes { get { return new string[] { "10px", "12px", "14px", "16px", "20px", "24px" }; } }


        public string Title
        {
            get { return _title; }
            set { _title = value; OnPropertyChanged("Title"); }
        }
        private string _title;

        public LocalSettings Settings
        {
            get 
            { 
                if (_settings == null)
                {
                    _settings = BlobDataManager.Instance.LoadSettings();
                }
                return _settings; 
            }
        }
        private LocalSettings _settings;

        public string SelectedFontSizes
        {
            get 
            {
                if (_SelectedFontSizes == null)
                {
                    _SelectedFontSizes = Settings.FontSize ?? "12px";
                }
                return _SelectedFontSizes; 
            }
            set 
            { 
                _SelectedFontSizes = value;

                Settings.FontSize = value;
                BlobDataManager.Instance.SaveSettings(Settings);
                OnPropertyChanged("SelectedFontSizes", "FontSize", "ImageWidthHeight", "ImagePanelWith"); 
            }
        }
        private string _SelectedFontSizes;


        public int FileNameWidth
        {
            get
            {
                return Settings.FileNameWidth;
            }
            set
            {
                if (Settings.FileNameWidth != value)
                {
                    Settings.FileNameWidth = value;
                    BlobDataManager.Instance.SaveSettings(Settings);
                }
            }
        }

        public int ModifiedDateWidth
        {
            get
            {
                return Settings.ModifiedDateWidth;
            }
            set
            {
                if (Settings.ModifiedDateWidth != value)
                {
                    Settings.ModifiedDateWidth = value;
                    BlobDataManager.Instance.SaveSettings(Settings);
                }
            }
        }

        public int ModifiedByWidth
        {
            get
            {
                return Settings.ModifiedByWidth;
            }
            set
            {
                if (Settings.ModifiedByWidth != value)
                {
                    Settings.ModifiedByWidth = value;
                    BlobDataManager.Instance.SaveSettings(Settings);
                }
            }
        }

        public int SizeWidth
        {
            get
            {
                return Settings.SizeWidth;
            }
            set
            {
                if (Settings.SizeWidth != value)
                {
                    Settings.SizeWidth = value;
                    BlobDataManager.Instance.SaveSettings(Settings);
                }
            }
        }

        [ExcludeFromCodeCoverage]

        public int ImageWidthHeight
        {
            get { return (int)(16 * FontSize / (double)new FontSizeConverter().ConvertFrom("14px")); }
        }
        [ExcludeFromCodeCoverage]
        public int ImagePanelWith
        {
            get { return (int)(ImageWidthHeight+8); }
        }

        public string CurrentPath
        {
            get
            {
                if (SelectedFolder == null) return null;
                return SelectedFolder.Path;
            }
            set
            {
                ExecuteActionAsync(ChangeCurrentPath(value));
                OnPropertyChanged("CurrentPath");
            }
        }

        public string SearchText
        {
            get { return _searchText; }
            set { _searchText = value; OnPropertyChanged("SearchText"); }
        }
        private string _searchText;

        public ObservableCollection<DirectoryItem> Folders { get; set; }

        public ObservableCollection<FileItem> Files { get; set; }

        public List<CachedFile> RecentFiles
        {
            get 
            { 
                return BlobDataManager.Instance.GetRecentFiles().Take(20).ToList();
            }
        }

        public List<CachedFile> EditingFiles
        {
            get
            {
                return BlobDataManager.Instance.GetEditingFiles();
            }
        }

        public List<String> FavoriteList
        {
            get
            {
                return BlobDataManager.Instance.LoadFavorites().OrderBy(x=>x.ToLowerInvariant()).ToList();
            }
        }


        [Browsable(false)]
        public DirectoryItem SelectedFolder
        {
            get { return _SelectedFolder; }
            set
            {
                _SelectedFolder = value;
                OnPropertyChanged("SelectedFolder");
            }
        }
        private DirectoryItem _SelectedFolder;


        [Browsable(false)]
        public FileItem SelectedFile
        {
            get { return _SelectedFile; }
            set
            {
                _SelectedFile = value;
                OnPropertyChanged("SelectedFile", "SearchPanelMaxHeight");
            }
        }
        private FileItem _SelectedFile;

        public int SearchPanelMaxHeight
        {
            get { return SelectedFile == null ? 0 : 200; }
        }

        public ICommand SelectedItemChangedCommand { get { return CreateCommand(SelectedItemChanged); } }
        public void SelectedItemChanged(object item)
        {
            SelectedFolder = item as DirectoryItem;
            ExecuteActionAsync(SetSelectedFolderAsync(SelectedFolder), null, string.Format(Resources.MsgSearchFiles, SelectedFolder.Title));
        }

        public ICommand ExecuteFileCommand
        {
            get
            {
                return CreateCommand((x) => {
                    var file = (FileItem)x ?? SelectedFile;
                    if (file.CheckoutUser == null && !BlobDataManager.Instance.Offline)
                    {
                        MessageBoxResult result = ShowYesNoCancel(string.Format(Resources.MsgEditOrOpen, file.Title));
                        if (result == MessageBoxResult.Cancel) return;
                        if (result == MessageBoxResult.Yes)
                        {
                            ExecuteActionAsync(CheckoutExecuteFile(file));
                            return;
                        }
                        else
                        {
                            ExecuteActionAsync(ExecuteFile(file, false));
                        }
                    }
                    else
                    {
                        if (file.CheckoutUser == BlobDataManager.Instance.LoginUser)
                        {
                            ExecuteActionAsync(ExecuteFile(file, true));
                        }
                        else
                        {
                            ExecuteActionAsync(ExecuteFile(file, false));
                        }
                    }
                });
            }
        }

        public ICommand CheckoutExecuteFileCommand
        {
            get
            {
                return CreateCommand((x) => { ExecuteActionAsync(CheckoutExecuteFile((FileItem)x ?? SelectedFile)); });
            }
        }
        private async Task CheckoutExecuteFile(FileItem data)
        {
            if (data == null) return;
            await BlobDataManager.Instance.CheckoutAsync(data.Path);
            data.CheckoutUser = BlobDataManager.Instance.LoginUser;
            await UpdateItemAsync(data);
            await ExecuteFile(data, true);
        }


        public ICommand RefreshCommand { get { return CreateCommand(RefreshFile, (x)=>Online); } }
        private void RefreshFile(object arg)
        {
            ExecuteActionAsync(SetSelectedFolderAsync(this.SelectedFolder, true), null, string.Format(Resources.MsgSearchFiles, SelectedFolder.Title));
        }

        public ICommand LoadCommand { get { return CreateCommand(Load); } }
        private void Load(object arg)
        {
            var dire = arg as DirectoryItem;
            if(dire != null)
            {
                ExecuteActionAsync(LoadFolder(dire), null, string.Format(Resources.MsgSearchFiles, dire.Title));
            }
        }
        private async Task LoadFolder(DirectoryItem value, bool refresh = false)
        {
            await value.LoadChildrenAsync(refresh);
        }

        public ICommand CurrentPathChangeCommand { get { return CreateCommand(CurrentPathChange); } }
        private void CurrentPathChange(object arg)
        {
            OnPropertyChanged("CurrentPath");
        }

        [Browsable(false)]
        public ICommand PreviousPathChangeCommand { get { return CreateCommand(PreviousPathChange, CanPreviousPathChange); } }
        private void PreviousPathChange(object arg)
        {
            if (histroyIndex == 0) return;
            try
            {
                stockHistory = false;
                ExecuteActionAsync(ChangeCurrentPath(BackHistory()));
            }
            finally
            {
                stockHistory = true;
            }
        }

        private bool CanPreviousPathChange(object arg)
        {
            return histroyIndex > 1;
        }

        [Browsable(false)]
        public ICommand ForwardPathChangeCommand { get { return CreateCommand(ForwardPathChange, CanForwardPathChange); } }
        private void ForwardPathChange(object arg)
        {
            if (histroyIndex >= history.Count) return;
            try
            {
                stockHistory = false;
                ExecuteActionAsync(ChangeCurrentPath(ForwardHistory()));
            }
            finally
            {
                stockHistory = true;
            }
        }

        private bool CanForwardPathChange(object arg)
        {
            return histroyIndex < history.Count;
        }

        public ICommand SearchCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    ExecuteActionAsync(SearchFileAsync(), null, string.Format(Resources.MsgSearchKeyword, SearchText));
                }, (x)=>Online);
            } 
        }
        private async Task SearchFileAsync()
        {
            this.Files.Clear();
            var files = await BlobDataManager.Instance.SearchFilesAsync(SelectedFolder.Path, SearchText, 0, 2000);
            foreach (var item in files.OfType<FileItem>())
            {
                this.Files.Add(item);
            }
            //UpdateRemark();
            OnPropertyChanged("Files");
        }

        public ICommand GetRecentCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    ExecuteActionAsync(GetRecentAsync(), null, Resources.MsgListRecent);
                }, (x) => Online);
            }
        }
        private async Task GetRecentAsync()
        {
            this.Files.Clear();
            var files = await BlobDataManager.Instance.GetRecentAsync();
            foreach (var item in files.OfType<FileItem>())
            {
                this.Files.Add(item);
            }
            //UpdateRemark();
            OnPropertyChanged("Files");
        }

        public ICommand CreateFolderCommand 
        { 
            get 
            { 
                return CreateCommand((x) => {
                    CreateDirectoryDialog.FolderName = "";
                    CreateDirectoryDialog.FontSize = this.FontSize;
                    ShowDialog(CreateDirectoryDialog, CloudStorageLight.WpfClient.Properties.Resources.MsgCreateFolder);
                    if (CreateDirectoryDialog.DialogResult)
                    {
                        var dire = CreateDirectoryDialog.FolderName.Trim();
                        ExecuteActionAsync(CreateFolder(dire));
                    }
                }); 
            } 
        }

        private bool IsValidPath(string path)
        {
            return !System.IO.Path.GetInvalidFileNameChars().Any(x => path.Contains(x));
        }

        private async Task CreateFolder(string dire)
        {
            if (string.IsNullOrEmpty(dire) || !IsValidPath(dire))
            {
                throw new ApplicationException(Resources.MsgInvalidDirectoryPath);   
            }

            await BlobDataManager.Instance.CreateFolderAsync(SelectedFolder.Path, dire);
            await SelectedFolder.LoadChildrenAsync(true);
        }

        public ICommand CopyCommand { get { return CreateCommand((x) => { ExecuteActionAsync(CopyOrMoveFile(x, false),multiAction:true); }); } }
        public ICommand MoveCommand { get { return CreateCommand((x) => { ExecuteActionAsync(CopyOrMoveFile(x, true), multiAction: true); }); } }
        private async Task CopyOrMoveFile(object arg, bool deleteSource = true)
        {
            var data = (FileCommandArg)arg;

            var path = (data.Path ?? SelectedFolder.Path);

            //親ディレクトリのコピーは除外
            var files =  data.Files.Where(x=>!x.EndsWith("/") || !path.StartsWith(GetDirectoryPath(x)));
            files = files.Where(x => path != GetDirectoryPath(x));

            await CopyOrMoveFiles(deleteSource, path, files.ToArray());

            //移動先が同じものは名前を変えてアップロード
            var uploadfiles = data.Files.Where(x => path == GetDirectoryPath(x));
            foreach (var uploadFile in uploadfiles)
            {
                for (int i = 1; i < 10000; i++)
                {
                    var fn = GetFileName(uploadFile);
                    var newFilename = GetDirectoryPath(uploadFile)
                        + System.IO.Path.GetFileNameWithoutExtension(fn) + "(" + i.ToString() + ")" + System.IO.Path.GetExtension(fn);
                    if (await BlobDataManager.Instance.GetFileAsync(newFilename) == null)
                    {
                        ProgressMessage = string.Format(Resources.MsgCopyFile,newFilename);
                        await BlobDataManager.Instance.CopyAsync(uploadFile, newFilename);
                        var updatedfileItem = (FileItem)await BlobDataManager.Instance.GetFileAsync(newFilename);
                        ExecuteUIProc(() =>
                        {
                            UpdatePanel(updatedfileItem);
                        });
                        break;
                    }
                }
            }

        }


        private async Task<bool> CopyOrMoveFiles(bool deleteSource, string targetPath, string[] files)
        {
            var idx = 0;
            List<Task> allTasks = new List<Task>();
            var copyList = new List<string>();
            var throttler = new SemaphoreSlim(initialCount: maxServerCall);

            foreach (var file in files.Where(x=>x.EndsWith("/")))
            {
                idx++;
                try
                {
                    if (IsCancelled) break;

                    Debug.WriteLine("MoveOrCopy: " + file + " => " + targetPath);

                    if (!await WhenAllWithCheckFailTask(allTasks)) break;   //処理中のものを待つ

                    //サブフォルダの作成
                    var parentDire = GetParentDirectoryPath(file);
                    var subDire = file.Substring(parentDire.Length).Replace("/", "");
                    ProgressMessage = string.Format(deleteSource ? Resources.MsgMoveFile : Resources.MsgCopyFile, subDire);
                    await BlobDataManager.Instance.CreateFolderAsync(targetPath, subDire, true);
                    //サブフォルダの中身を処理
                    var subfiles = await BlobDataManager.Instance.GetFilesAsync(file, 0);
                    await CopyOrMoveFiles(deleteSource, targetPath + subDire + "/", subfiles.Select(x => x.Path).ToArray());
                    if (deleteSource)
                    {
                        await BlobDataManager.Instance.DeleteAsync(file);
                        ExecuteUIProc(() =>
                        {
                            var folder = FindItem(GetParentDirectoryPath(file)) as DirectoryItem;
                            if (folder != null)
                            {
                                var folderItem = folder.Folders.Where(x => x.Path == file).FirstOrDefault();
                                if (folderItem != null) folder.Folders.Remove(folderItem);
                            }
                        });
                    }

                }
                catch (TaskCanceledException)
                {
                    this.Message = Resources.MsgCancelled;
                    break;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) break;
                }
            }
            foreach (var file in files.Where(x => !x.EndsWith("/")))
            {
                idx++;
                try
                {
                    if (IsCancelled) break;

                    Debug.WriteLine("MoveOrCopy: " + file + " => " + targetPath);

                    await throttler.WaitAsync();

                    lock (copyList)
                    {
                        copyList.Add(GetFileName(file));
                        ProgressMessage = GetProcessingMessage(deleteSource ? Resources.MsgMoveFile : Resources.MsgCopyFile, copyList);
                    }

                    allTasks.Add(Task.Run(async () =>
                    {
                        try
                        {
                            if (deleteSource)
                            {
                                await BlobDataManager.Instance.MoveAsync(file, targetPath);
                                ExecuteUIProc(() =>
                                {

                                    var folder = FindItem(GetParentDirectoryPath(file)) as DirectoryItem;
                                    if (folder != null)
                                    {
                                        var fileItem = folder.Files.Where(x => x.Path == file).FirstOrDefault();
                                        if (fileItem != null) folder.Files.Remove(fileItem);
                                        var folderItem = folder.Folders.Where(x => x.Path == file).FirstOrDefault();
                                        if (folderItem != null) folder.Folders.Remove(folderItem);
                                        var filesPanelItem = Files.Where(x => x.Path == file).FirstOrDefault();
                                        if (filesPanelItem != null) Files.Remove(filesPanelItem);

                                    }
                                });
                            }
                            else
                            {
                                await BlobDataManager.Instance.CopyAsync(file, targetPath);
                            }
                            var targetFilePath = targetPath + GetFileName(file);
                            var updatedfileItem = (FileItem)await BlobDataManager.Instance.GetFileAsync(targetFilePath);
                            ExecuteUIProc(() =>
                            {
                                UpdatePanel(updatedfileItem);
                            });
                            lock (copyList)
                            {
                                copyList.Remove(GetFileName(file));
                                ProgressMessage = GetProcessingMessage(deleteSource ? Resources.MsgMoveFile : Resources.MsgCopyFile, copyList);
                            }
                        }
                        finally
                        {
                            throttler.Release();
                        }
                    }));

                    CheckFailTask(allTasks);

                }
                catch (TaskCanceledException)
                {
                    this.Message = Resources.MsgCancelled;
                    break;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) break;
                }
            }
            return await WhenAllWithCheckFailTask(allTasks);

        }

        private string GetProcessingMessage(string format, List<string> list)
        {
            if (list.Count == 0)
            {
                return Resources.MsgProcessing;
            }
            else
            {
                lock(list)
                {
                    return string.Join("\n", list.Select(x => string.Format(format, x)));
                }
            }

        }

        [ExcludeFromCodeCoverage]
        private bool CheckFailTask(List<Task> allTasks)
        {
            //終了タスクのエラー確認
            foreach (var task in allTasks.Where(x => x.IsCompleted).ToList())
            {
                if (IsCancelled) break;
                if (task.IsFaulted)
                {
                    if (!ShowError(task.Exception.InnerException)) return false;
                }
                allTasks.Remove(task);
            }
            return true;
        }

        private async Task<bool> WhenAllWithCheckFailTask(List<Task> allTasks)
        {
            //終了タスクのエラー確認
            foreach (var task in allTasks.ToList())
            {
                if (IsCancelled) return false;
                try
                {
                    await Task.WhenAll(task);
                }
                catch (OperationCanceledException)
                {
                    return false;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) return false;
                }
                allTasks.Remove(task);
            }
            return true;
        }

        [Browsable(false)]
        public ICommand CheckinCommand { get { return CreateCommand((x) => { ExecuteActionAsync(CheckinAsync((FileItem)x ?? SelectedFile, false)); }); } }
        private async Task<bool> CheckinAsync(FileItem data, bool delta)
        {
            var localCached = BlobDataManager.Instance.GetCachedFile(data);
            var file = (FileItem)await BlobDataManager.Instance.GetFileAsync(data.Path);
            if (file == null) throw new ApplicationException(Resources.MsgNotFound);
            if (!file.IsCheckout) {
                data.CheckoutUser = null;
                await UpdateItemAsync(data);
                if (file.IsLocalEditing)
                {
                    this.ShowMessage(Resources.MsgNotCheckoutEditing);
                }
                else
                {
                    this.ShowMessage(Resources.MsgNoCheckout);
                }
                return false;
            }

            if (localCached != null && localCached.IsDirty)
            {
                if ((localCached != null) && File.Exists(localCached.LocalPath))
                {
                    var progress = new Progress<double>();
                    progress.ProgressChanged += (s, e) =>
                    {
                        ProgressMessage = string.Format(Resources.MsgUploadFile, System.IO.Path.GetFileName(data.Path)) + string.Format(" {0:0}%", e);
                    };
                    UploadInfo uploadInfo;
                    uploadInfo = await BlobDataManager.Instance.UploadFileAsync(GetDirectoryPath(data.Path), localCached.LocalPath, progress);
                    UpdateLocalWriteTime(localCached, uploadInfo);
                }
            }
            await BlobDataManager.Instance.CheckinAsync(data.Path);
            data.CheckoutUser = null;
            await UpdateItemAsync(data);
            if (localCached != null && localCached.IsEditing)
            {
                SetReadOnly(localCached.LocalPath);
                OnPropertyChanged("EditingFiles");
            }
            return true;
        }

        private void UpdateLocalWriteTime(CachedFile localCached, UploadInfo uploadInfo)
        {
            if (uploadInfo.LastWriteTime.HasValue)
            {
                var finfo = new FileInfo(localCached.LocalPath);
                if (finfo.IsReadOnly)
                {
                    SetReadWrite(localCached.LocalPath);
                }
                File.SetLastWriteTimeUtc(localCached.LocalPath, uploadInfo.LastWriteTime.Value);
                if (finfo.IsReadOnly)
                {
                    SetReadOnly(localCached.LocalPath);
                }
                localCached.LastModified = uploadInfo.LastWriteTime.Value;
            }
            var info = new FileInfo(localCached.LocalPath);
            localCached.LocalFileCachedTime = info.LastWriteTime;
            BlobDataManager.Instance.SaveCachedFile();
        }

        [Browsable(false)]
        public ICommand UploadFileCommand { get { return CreateCommand((x) => { ExecuteActionAsync(UploadFileAsync((FileItem)x ?? SelectedFile), multiAction: true); }); } }
        private async Task UploadFileAsync(FileItem data)
        {
            if (data == null) return;
            var progress = new Progress<double>();
            progress.ProgressChanged += (s, e) =>
            {
                ProgressMessage = string.Format(Resources.MsgUploadFile, System.IO.Path.GetFileName(data.Path)) + string.Format(" {0:0}%", e);
            };
            var localCached = BlobDataManager.Instance.GetCachedFile(data);
            if (localCached == null) return;
            var uploadInfo = await BlobDataManager.Instance.UploadFileAsync(GetDirectoryPath(data.Path), localCached.LocalPath,progress);
            UpdateLocalWriteTime(localCached, uploadInfo);
            data.RaisePropertyChanged(null);
        }


        [Browsable(false)]
        public ICommand CancelCheckoutCommand { get { return CreateCommand((x) => { ExecuteActionAsync(CancelCheckoutAsync((FileItem)x ?? SelectedFile)); }); } }
        private async Task<bool> CancelCheckoutAsync(FileItem data)
        {
            if (data == null) return false;
            var cache = BlobDataManager.Instance.GetCachedFile(data);
            if (cache != null)
            {
                if (cache.IsDirty)
                {
                    if (!this.ShowOKCancel(string.Format(Resources.MsgConfirmCancelCheckout, GetFileName(data.Path)))) return false;
                }
            }
            var file = (FileItem)await BlobDataManager.Instance.GetFileAsync(data.Path);
            if (file != null)
            {
                if (file.IsCheckout)
                {
                    await BlobDataManager.Instance.CheckinAsync(data.Path);
                }
            }
            data.CheckoutUser = null;
            BlobDataManager.Instance.ClearCachedFile(data);
            await UpdateItemAsync(data);

            return file != null && file.IsCheckout;
        }

        [Browsable(false)]
        public ICommand CheckoutCommand { get { return CreateCommand((x) => { ExecuteActionAsync(CheckoutAsync(x)); }); } }
        private async Task<bool> CheckoutAsync(object arg)
        {
            var data = (FileItem)arg;
            await BlobDataManager.Instance.CheckoutAsync(data.Path);
            data.CheckoutUser = BlobDataManager.Instance.LoginUser;
            await UpdateItemAsync(data);
            return true;
        }

        [Browsable(false)]
        public ICommand ReloadCommand { get { return CreateCommand((x) => { ExecuteActionAsync(ReloadAsync((FileItem)x ?? SelectedFile)); }); } }
        private async Task<bool> ReloadAsync(FileItem data)
        {
            if (data == null) return false;
            if (!ConfirmDirtyOverwrite(data)) return false;
            BlobDataManager.Instance.ClearCachedFile(data);
            var cachedData = BlobDataManager.Instance.CreateCacheFileEntry(data);

            var progress = new Progress<double>();
            progress.ProgressChanged += (s, e) =>
            {
                ProgressMessage = string.Format(Resources.MsgDownloadFile, System.IO.Path.GetFileName(data.Path)) + string.Format(" {0:0}%", e);
            };

            var info = await BlobDataManager.Instance.DownloadAsync(data.Path, cachedData.LocalPath, "current", data.DownloadMode, progress, data.Length);
            if (info.LastWriteTime.HasValue) data.LastModified = info.LastWriteTime.Value;
            if (!data.IsCheckout) SetReadOnly(cachedData.LocalPath);
            BlobDataManager.Instance.SetCacheDownloaded(data, info);

            return true;
        }


        private async Task UpdateItemAsync(FileItem item, bool reload=false)
        {
            if (reload)
            {
                var file = (FileItem)await BlobDataManager.Instance.GetFileAsync(item.Path);
                item.Owner = file.Owner;
                item.LastModified = file.LastModified;
                item.CheckoutUser = file.CheckoutUser;
                item.Length = file.Length;
                item.FileProtectedMode = file.FileProtectedMode;
                item.AttachmentFileName = file.AttachmentFileName;
                item.Remark = file.Remark;
                item.Title = file.Title;
            }
            //UpdateRemark();
            item.RaisePropertyChanged(null);
        }


        [Browsable(false)]
        public ICommand RenameFileCommand { get { return CreateCommand((x) => { ExecuteActionAsync(RenameFileAsync(x)); }); } }
        private async Task<bool> RenameFileAsync(object arg)
        {
            var data = (FileItem)arg;
            try
            {
                if (data.IsLocalEditing) throw new ApplicationException(Properties.Resources.MsgCanNotOpeEdtingFile); 
                var newPath = GetDirectoryPath(data.Path) + (string)data.Title;
                if (!data.IsLocalOnly) await BlobDataManager.Instance.RenameAsync(data.Path, newPath);
                var cached = BlobDataManager.Instance.RenameCachedFile(data, GetDirectoryPath(data.Path) + (string)data.Title);
                data.Id = ConvertPathToId(newPath);
                data.Path = newPath;
                data.AttachmentFileName = System.IO.Path.GetFileName(newPath);
                await UpdateItemAsync(data);
                return true;
            }
            catch 
            {
                data.Title = data.OldTitle;
                throw;
            }
        }
        internal static string ConvertPathToId(string path)
        {
            return path.Replace("/", "|").Replace(".", "*");
        }


        [Browsable(false)]
        public ICommand RenameFolderCommand 
        {
            get { return CreateCommand((x) => { ExecuteActionAsync(RenameFolder((DirectoryItem)x)); }); } 
        }
        private async Task RenameFolder(DirectoryItem item)
        {
            var parentFolder = GetDirectoryPath(item.Path.TrimEnd('/'));
            var newPath = parentFolder + item.Title + "/";
            var oldPath = parentFolder + item.OldTitle + "/";

            //親ディレクトリのコピーは除外
            var files = await BlobDataManager.Instance.GetFilesAsync(oldPath, 0);
            await BlobDataManager.Instance.CreateFolderAsync(parentFolder, item.Title, true);
            await CopyOrMoveFiles(true, newPath, files.Select(x => x.Path).ToArray());

            await BlobDataManager.Instance.DeleteAsync(oldPath);
            ExecuteUIProc(() =>
            {
                var folder = FindItem(GetParentDirectoryPath(oldPath)) as DirectoryItem;
                if (folder != null)
                {
                    var folderItem = folder.Folders.Where(x => x.Path == oldPath).FirstOrDefault();
                    if (folderItem != null) folder.Folders.Remove(folderItem);
                }
            });
        }

      
        [Browsable(false)]
        public ICommand DeleteFileCommand { get { return CreateCommand((x) => { ExecuteActionAsync(DeleteFileAsync((FileCommandArg)x), multiAction: true); }); } }
        private async Task<bool> DeleteFileAsync(FileCommandArg data)
        {
            var comp = data.Data.ContainsKey("Completely") && (bool)data.Data["Completely"];
            var files = new List<TreeItem>();
            foreach (var file in data.Files.Where(x => !x.EndsWith("/")))
            {
                var item = FindItem(Folders, file);
                if (item == null) continue;
                files.Add(item);
            }
            if (!await DeleteFilesAsync(comp,files)) return false;

            foreach (var file in data.Files.Where(x => x.EndsWith("/")))
            {
                try
                {
                    if (IsCancelled) break;

                    var item = FindItem(Folders, file);
                    if (item == null) continue;
                    if (!await DeleteDirectoryAsync(comp, (DirectoryItem)item)) return false;
                }
                catch (TaskCanceledException)
                {
                    return false;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) return false;
                }
            }
            return true;
        }
        private async Task<bool> DeleteDirectoryAsync(bool comp, DirectoryItem item)
        {
            await item.LoadChildrenAsync(true);
            if (!await DeleteFilesAsync(comp, item.Files.ToList())) return false;

            foreach (var child in item.Folders.ToList())
            {
                try
                {
                    if (IsCancelled) break;

                    if (!await DeleteDirectoryAsync(comp, child)) return false;
                    RemoveItem(child);
                }
                catch (TaskCanceledException)
                {
                    return false;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) return false;
                }
            }

            if (item.Folders.Count == 0 && item.Files.Count == 0)
            {
                ProgressMessage = string.Format(Resources.MsgDeletingFile, item.Title);
                await BlobDataManager.Instance.DeleteAsync(item.Path);
                RemoveItem(item);
            }

            return true;
        }

        private async Task<bool> DeleteFilesAsync(bool comp, IEnumerable<TreeItem> items)
        {
            List<Task> allTasks = new List<Task>();
            var deletingList = new List<string>();
            var throttler = new SemaphoreSlim(initialCount: maxServerCall);

            foreach (var child in items)
            {
                try
                {
                    if (IsCancelled) break;

                    Debug.WriteLine("DeleteFilesAsync: " + child );

                    await throttler.WaitAsync();

                    lock (deletingList)
                    {
                        deletingList.Add(child.Title);
                        ProgressMessage = GetProcessingMessage(Resources.MsgDeletingFile, deletingList);
                    }
                    allTasks.Add(Task.Run(async () =>
                    {
                        try
                        {
                            var file = child as FileItem;
                            if (file != null && !file.IsLocalOnly)
                            {
                                if (comp)
                                {
                                    await BlobDataManager.Instance.DeleteCompletelyAsync(child.Path);
                                }
                                else
                                {
                                    await BlobDataManager.Instance.DeleteAsync(child.Path);
                                }
                            }
                            BlobDataManager.Instance.ClearCachedFile(child.Path);
                            ExecuteUIProc(() =>
                            {
                                RemoveItem(child);
                            }); 
                            lock (deletingList)
                            {
                                deletingList.Remove(child.Title);
                                ProgressMessage = GetProcessingMessage(Resources.MsgDeletingFile, deletingList);
                            }
                        }
                        finally
                        {
                            throttler.Release();
                        }
                    }));
                    CheckFailTask(allTasks);

                }
                catch (TaskCanceledException)
                {
                    this.Message = Resources.MsgCancelled;
                    break;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) break;
                }

            }
            return await WhenAllWithCheckFailTask(allTasks);
        }

        public ICommand UploadStreamCommand { get { return CreateCommand((x) => { ExecuteActionAsync(UploadStreamAsync((FileCommandArg)x), multiAction: true); }); } }
        private async Task<bool> UploadStreamAsync(FileCommandArg arg)
        {
            arg.Data["Result"] = null;
            try
            {
                //TODO BUSY
                var path = (arg.Path ?? SelectedFolder.Path);
                foreach (var filename in arg.Files)
                {
                    var uploadInfo = await BlobDataManager.Instance.UploadFileAsync(path, filename, ((Dictionary<string, Stream>)arg.Data["Stream"])[filename], DateTime.UtcNow, DateTime.UtcNow);
                    var f = await BlobDataManager.Instance.GetFileAsync(uploadInfo.UploadedPath);
                    UpdatePanel((FileItem)f);
                }
            }
            catch (Exception ex)
            {
                arg.Data["Result"] = ex;
                throw;
            }

            return true;
        }

        public ICommand UploadCommand { get { return CreateCommand((x) => { ExecuteActionAsync(UploadAsync((FileCommandArg)x), multiAction: true); }); } }
        private async Task<bool> UploadAsync(FileCommandArg arg)
        {
            var path = (arg.Path ?? SelectedFolder.Path);
            var newer = arg.Data.ContainsKey("Newer") ? (bool)arg.Data["Newer"] : false;
            var sync = arg.Data.ContainsKey("Sync") ? (bool)arg.Data["Sync"] : false;
            var confirm = arg.Data.ContainsKey("Confirm") ? (bool)arg.Data["Confirm"] : false;
            List<TreeItem> cloudFiles = null;
            List<TreeItem> rootCloudFiles = null;

            List<Task> allTasks = new List<Task>();
            var uploadList = new Dictionary<string, double>();
            var throttler = new SemaphoreSlim(initialCount: maxServerCall);

            foreach (var file in arg.Files)
            {

                if (IsCancelled) break;
                if (File.GetAttributes(file).HasFlag(FileAttributes.Hidden)) continue;

                if (Directory.Exists(file))
                {
                    //var newDire = System.IO.Path.GetFileName(file);
                    //await BlobDataManager.Instance.CreateFolderAsync(path, newDire, true);
                    var preDirec = path.TrimEnd('/') + "/";

                    if (newer)
                    {
                        lock (uploadList)
                        {
                            if (uploadList.Count == 0)
                            {
                                ProgressMessage = String.Format(Resources.MsgSearchFiles, preDirec);
                            }
                        }
                        cloudFiles = await BlobDataManager.Instance.GetFilesAsync(preDirec, 0);
                    }
                    DirectoryInfo directory = new DirectoryInfo(file);
                    var files = directory.GetFiles("*", System.IO.SearchOption.AllDirectories).Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden)).Select(x => x.FullName);
                    foreach (var subfile in files)
                    {
                        if (!BlobDataManager.CanUploadFile(subfile)) continue;
                        try
                        {
                            if (IsCancelled) break;
                            if (File.GetAttributes(subfile).HasFlag(FileAttributes.Hidden)) continue;

                            var subDire = System.IO.Path.GetDirectoryName(subfile).Substring(file.Length).Replace("\\", "/").TrimStart('/');
                            var subPath = path + subDire;

                            if (!subPath.EndsWith("/")) subPath = subPath + "/";


                            if (preDirec != subPath)
                            {
                                var newSubDire = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(subfile));
                                var parentDire = GetParentDirectoryPath(subPath);
                                await BlobDataManager.Instance.CreateFolderAsync(parentDire, newSubDire, true);
                                preDirec = subPath;
                                if (newer)
                                {
                                    lock (uploadList)
                                    {
                                        if (uploadList.Count == 0)
                                        {
                                            ProgressMessage = String.Format(Resources.MsgSearchFiles, preDirec);
                                        }
                                    }
                                    cloudFiles = await BlobDataManager.Instance.GetFilesAsync(preDirec, 0);

                                }
                            }

                            if (sync && System.IO.Path.GetExtension(subfile) == ".zip")
                            {
                                var filename = System.IO.Path.GetFileName(subfile);
                                var cloudZipFile = cloudFiles.Where(x => x.Path == subPath + filename.Substring(0,filename.Length-4)).FirstOrDefault();
                                if (cloudZipFile != null && cloudZipFile.FileProtectedMode != "None") continue;
                            }
                            TreeItem cloudFile = null;
                            if (newer)
                            {
                                cloudFile = cloudFiles.Where(x => IsMatchCloudFile(x, subPath, System.IO.Path.GetFileName(subfile))).FirstOrDefault();
                                var info = new FileInfo(subfile);
                                if (cloudFile != null && cloudFile.LastModified >= info.LastWriteTimeUtc) continue;
                            }

                            if (confirm)
                            {
                                if (cloudFile != null)
                                {
                                    if (!this.ShowOKCancel(string.Format(Resources.MsgConfirmOverwrite, System.IO.Path.GetFileName(subfile)))) continue;
                                }
                            }

                            var progress = new Progress<double>();
                            progress.ProgressChanged += (s, e) =>
                            {
                                lock (uploadList)
                                {
                                    uploadList[subfile] = e;
                                }
                                ProgressMessage = GetUploadProcessingMessage(uploadList);
                            };
                            await throttler.WaitAsync();
                            lock (uploadList)
                            {
                                uploadList[subfile] = 0;
                            }
                            ProgressMessage = GetUploadProcessingMessage(uploadList);

                            allTasks.Add(Task.Run(async () =>
                            {
                                try
                                {
                                    var info = await UploadItem(subfile, subPath, progress);
                                    ProgressMessage = GetUploadProcessingMessage(uploadList);

                                }
                                finally
                                {
                                    throttler.Release();
                                    lock (uploadList)
                                    {
                                        if (uploadList.ContainsKey(subfile)) uploadList.Remove(subfile);
                                    }
                                }
                            }));
                             CheckFailTask(allTasks);
                        }
                        catch (TaskCanceledException)
                        {
                            this.Message = Resources.MsgCancelled;
                            break;
                        }
                        catch (Exception ex)
                        {
                            if (!ShowError(ex)) break;
                        }

                    }
                }
                else
                {
                    try
                    {
                        if (sync && System.IO.Path.GetExtension(file) == ".zip")
                        {
                            var filename = System.IO.Path.GetFileName(file);
                            var cloudZipFile = rootCloudFiles == null ? null : cloudFiles.Where(x => x.Path == path + filename.Substring(0, filename.Length - 4)).FirstOrDefault();
                            if (cloudZipFile != null && cloudZipFile.FileProtectedMode != "None") continue;
                        }

                        rootCloudFiles = rootCloudFiles ?? await BlobDataManager.Instance.GetFilesAsync(path, 0);
                        var cloudFile = rootCloudFiles.Where(x => IsMatchCloudFile(x, path, System.IO.Path.GetFileName(file))).FirstOrDefault();
                        if (newer)
                        {
                            var info = new FileInfo(file);
                            if (cloudFile != null && cloudFile.LastModified >= info.LastWriteTimeUtc) continue;
                        }
                        if (confirm)
                        {
                            if (cloudFile != null)
                            {
                                if (!this.ShowOKCancel(string.Format(Resources.MsgConfirmOverwrite, System.IO.Path.GetFileName(file)))) continue;
                            }
                        }


                        var progress = new Progress<double>();
                        progress.ProgressChanged += (s, e) =>
                        {
                            lock (uploadList)
                            {
                                uploadList[file] = e;
                            }
                            ProgressMessage = GetUploadProcessingMessage(uploadList);
                        };
                        await throttler.WaitAsync();
                        lock (uploadList)
                        {
                            uploadList[file] = 0;
                        }
                        ProgressMessage = GetUploadProcessingMessage(uploadList);

                        allTasks.Add(Task.Run(async () =>
                        {
                            try
                            {
                                var info = await UploadItem(file, path, progress);
                                ProgressMessage = GetUploadProcessingMessage(uploadList);
                            }
                            finally
                            {
                                throttler.Release();
                                lock (uploadList)
                                {
                                    if (uploadList.ContainsKey(file)) uploadList.Remove(file);
                                }
                            }
                        }));
                        CheckFailTask(allTasks);

                    }
                    catch (TaskCanceledException)
                    {
                        this.Message = Resources.MsgCancelled;
                        break;
                    }
                    catch (Exception ex)
                    {
                        if (!ShowError(ex)) break;
                    }
                } 
            }
            return await WhenAllWithCheckFailTask(allTasks);

        }

        private bool IsMatchCloudFile(TreeItem item, string targetFolder, string fileName)
        {
            if (item.AttachmentFileName != fileName) return false;
            var parent = GetDirectoryPath(item.Path);
            if (targetFolder != parent) return false;
            return true;
        }

        private string GetUploadProcessingMessage(Dictionary<string, double> uploadList)
        {
            var sb = new StringBuilder();

            lock(uploadList)
            {
                foreach (var item in uploadList)
                {
                    var pro = item.Value > 0 ? string.Format(" {0:0}% ", item.Value) : "";
                    sb.AppendLine(string.Format( Resources.MsgUploadFile, System.IO.Path.GetFileName(item.Key)) + pro);
                }
            }
            return sb.ToString();
        }

        public ICommand UploadFolderCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    var path = (string)x;
                    if (path != null)
                    {
                        var arg = new FileCommandArg() { Files = new string[]{ path }};
                        ExecuteActionAsync(UploadAsync(arg), multiAction: true);
                        return;
                    }

                    if (path == null)
                    {
                        path = ShowFolderDailog();
                        if (path != null)
                        {
                            var newer = this.ShowYesNo(Resources.MsgConfirmToUploadNewerFiles);

                            var arg = new FileCommandArg { Files = new string[] { path } };
                            arg.Data.Add("Newer", newer);
                            ExecuteActionAsync(UploadAsync(arg));
                            return;
                        }
                    }

                });
            }
        }



        private async Task<UploadInfo> UploadItem(string file, string path, Progress<double> progress)
        {

            var filePath = path + System.IO.Path.GetFileName(file);
            var cache = BlobDataManager.Instance.GetCachedFile(filePath);
            if (cache != null && file != cache.LocalPath)
            {
                BlobDataManager.Instance.ClearCachedFile(filePath);
                cache = null;
            }
            var uploadInfo = await BlobDataManager.Instance.UploadFileAsync(path, file, progress);
            var fileItem = (FileItem)await BlobDataManager.Instance.GetFileAsync(uploadInfo.UploadedPath);

            if (cache != null)
            {
                UpdateLocalWriteTime(cache, uploadInfo);
            }

            ExecuteUIProc(() => {
                UpdatePanel(fileItem);
            });

            return uploadInfo;

        }

        private void UpdatePanel(FileItem fileItem)
        {
            if (fileItem != null)
            {
                if (GetParentDirectoryPath(fileItem.Path) == SelectedFolder.Path)
                {
                    var filedata = this.Files.Where(x => x.Title == fileItem.Title).FirstOrDefault();
                    if (filedata != null)
                    {
                        filedata.Length = fileItem.Length;
                        filedata.Owner = fileItem.Owner;
                        filedata.CheckoutUser = fileItem.CheckoutUser;
                        filedata.RaisePropertyChanged(null);
                    }
                    else
                    {
                        this.Files.Add((FileItem)fileItem);
                        //UpdateRemark();                    
                    }
                }

                var dire = (DirectoryItem)FindItem(GetParentDirectoryPath(fileItem.Path));
                if (dire != null)
                {
                    var filedata = dire.Files.Where(x => x.Path == fileItem.Path).FirstOrDefault();
                    if (filedata != null)
                    {
                        filedata.Length = fileItem.Length;
                        filedata.Owner = fileItem.Owner;
                        filedata.CheckoutUser = fileItem.CheckoutUser;
                    }
                    else
                    {
                        dire.Files.Add((FileItem)fileItem);
                    }
                }
                else
                {
                    AddTreeViewFolder(GetParentDirectoryPath(fileItem.Path));
                }
            }
        }


        private void AddTreeViewFolder(string direPath)
        {
            var parentPath = GetParentDirectoryPath(direPath);
            var dire = (DirectoryItem)FindItem(parentPath);
            if (dire == null)
            {
                AddTreeViewFolder(parentPath);
                if (parentPath != "/") dire = (DirectoryItem)FindItem(parentPath);
            }
            if (!dire.Folders.Any(x=>x.Path == direPath))
            {
                dire.Folders.Add(new DirectoryItem { Path = direPath, Title = GetDirectoryName(direPath) });
            }

        }


        public ICommand DownloadFolderCommand 
        { 
            get { 
                    return CreateCommand((x) => {
                        var path = (string)x;
                        if (path != null)
                        {
                            ExecuteActionAsync(DownloadFolder(path, false), multiAction: true);
                            return;
                        }
                        
                        path = ShowFolderDailog();
                        if (path != null)
                        {
                            ExecuteActionAsync(DownloadFolder(path, false), multiAction: true);
                        }

                    }); 
            } 
        }

        [ExcludeFromCodeCoverage]
        protected virtual string ShowFolderDailog()
        {
            var dialog = new System.Windows.Forms.FolderBrowserDialog();
            var result = dialog.ShowDialog();
            if (result != System.Windows.Forms.DialogResult.OK)
            {
                return null;
            }
            else
            {
                return dialog.SelectedPath;
            }

        }
        private async Task DownloadFolder(string targetFolder, bool selectfiles)
        {
            var newer = this.ShowYesNo(Resources.MsgConfirmToDownloadNewerFiles);
            await DownloadFolder(targetFolder, SelectedFolder.Path, newer);
        }

        private async Task DownloadFolder(string targetFolder, string sourcePath, bool newer)
        {
            ProgressMessage = String.Format(Resources.MsgSearchFiles, sourcePath);
            var files = await BlobDataManager.Instance.GetFilesAsync(sourcePath, 0);
            await DownloadFiles(targetFolder, files.OfType<FileItem>(), newer, true);
            foreach (var folder in files.OfType<DirectoryItem>())
            {
                await DownloadFolder(System.IO.Path.Combine(targetFolder, GetDirectoryName(folder.Path)), folder.Path, newer);
            }
        }


        [ExcludeFromCodeCoverage]
        protected virtual bool ShowYesNo(string message, string title = "confirm")
        {
            return MessageBox.Show(message, title, MessageBoxButton.YesNo) == MessageBoxResult.Yes;
        }

        private async Task<IEnumerable<FileItem>> ConvertFileItem(IEnumerable<string> files)
        {
            var list = new List<FileItem>();
            foreach (var file in files)
            {
                var item = FindItem(file);
                if (item == null)
                {
                    list.Add((FileItem)await BlobDataManager.Instance.GetFileAsync(file)); 
                }
                else
                {
                    list.Add((FileItem)item); 
                }
            }
            return list;
        }

        protected async Task<bool> DownloadFiles(string targetFolder, IEnumerable<FileItem> files,bool newerOnly=false, bool skipProtectedZip = false)
        {
            List<Task> allTasks = new List<Task>();
            var downloadList = new Dictionary<string,double>();
            var throttler = new SemaphoreSlim(initialCount: maxServerCall);


            foreach (var file in files)
            {
                try
                {
                    if (IsCancelled) break;

                    var fileItem = new FileItem { Path = file.Path, Title = file.Title, FileProtectedMode = file.FileProtectedMode, AttachmentFileName = file.AttachmentFileName };
                    var targetPath = System.IO.Path.Combine(targetFolder, file.AttachmentFileName);
                    var cachedData = BlobDataManager.Instance.GetCachedFile(file);

                    var progress = new Progress<double>();
                    progress.ProgressChanged += (s, e) =>
                    {
                        lock(downloadList)
                        {
                            downloadList[file.Path] = e;
                        }
                        ProgressMessage = GetDownloadProcessingMessage(downloadList);
                    };

                    var confirm = false;
                    if (newerOnly && File.Exists(targetPath))
                    {
                        var info = new FileInfo(targetPath);
                        //サーバ側が新しい
                        if (info.LastWriteTimeUtc< file.LastModified)
                        {
                            if (cachedData == null || targetPath != cachedData.LocalPath || ConfirmDirtyOverwrite(file))
                            {
                                confirm = true;
                            }
                        }
                    }
                    else
                    {
                        //常に上書きかターゲット無の場合はサーバから
                        if (cachedData == null || targetPath != cachedData.LocalPath || ConfirmDirtyOverwrite(file))
                        {
                            confirm = true;
                        }
                    }

                    if (confirm)
                    {
                        await throttler.WaitAsync();
                        allTasks.Add(Task.Run(async () =>
                        {
                            try
                            {
                                lock (downloadList)
                                {
                                    downloadList[file.Path] = 0;
                                }
                                ProgressMessage = GetDownloadProcessingMessage(downloadList);

                                await BlobDataManager.Instance.DownloadAsync(file.Path, targetPath, null, fileItem.DownloadMode, progress, file.Length);
                                lock (downloadList)
                                {
                                    if (downloadList.ContainsKey(file.Path)) downloadList.Remove(file.Path);
                                }
                                ProgressMessage = GetDownloadProcessingMessage(downloadList);
                            }
                            finally
                            {
                                throttler.Release();
                            }
                        }));
                    }

                    
                    CheckFailTask(allTasks);

                }
                catch (TaskCanceledException)
                {
                    this.Message = Resources.MsgCancelled;
                    break;
                }
                catch (Exception ex)
                {
                    if (!ShowError(ex)) break;
                }
            }
            return await WhenAllWithCheckFailTask(allTasks);
        }

        private string GetDownloadProcessingMessage(Dictionary<string,double> downloadList)
        {
            var sb = new StringBuilder();

            lock(downloadList)
            {
                foreach (var item in downloadList)
                {
                    var pro = item.Value > 0 ? string.Format(" {0:0}% ", item.Value) : "";
                    sb.AppendLine(string.Format(Resources.MsgDownloadFile, GetFileName(item.Key)) + pro);
                }
            }
            return sb.ToString();            
        }

        private bool ConfirmDirtyOverwrite(FileItem data)
        {
            var cachedData = BlobDataManager.Instance.GetCachedFile(data);
            if (cachedData != null && cachedData.IsDirty) //ローカル変更なし
            {
                if (!this.ShowOKCancel(string.Format(Resources.MsgOverwriteEdtingFile, data.Title))) return false;
            }
            return true;
        }


        private string ConvertUrlPathToWindowsPath(string urlPath)
        {
            return urlPath.Replace("/","\\");
        }

        private string ConvertWindowsPathToUrlPath(string winPath)
        {
            return winPath.Replace("\\", "/");
        }

        public ICommand ShowDirectorySetupCommand { get { return CreateCommand((x) => { ExecuteActionAsync(ShowDirectorySetupAsync(x)); }); } }
        private async Task ShowDirectorySetupAsync(object arg)
        {
            DirectorySetupDialog.Path = this.CurrentPath;
            var accessTokens = await BlobDataManager.Instance.GetAccessControlAsync(this.CurrentPath);
            DirectorySetupDialog.SetAccessTokenString(accessTokens);
            ShowDialog(DirectorySetupDialog);
            if (DirectorySetupDialog.Updated)
            {
                await BlobDataManager.Instance.SetAccessControlAsync(this.CurrentPath, DirectorySetupDialog.GetAccessTokenString());
            }
        }

        public ICommand ShowHistoryItemCommand { get { return CreateCommand((x) => { ExecuteActionAsync(ShowDetailItemAsync(SelectedFile),isBusy:false); }); } }
        private async Task ShowDetailItemAsync(FileItem item)
        {
            if (item == null) return;
            var history = await BlobDataManager.Instance.GetVersion(item.Path);
            if (history.Count > 0)
            {
                var detail = new HistoryVM(item.Path, item.Title, history, item.FileProtectedMode, item.AttachmentFileName);
                ShowDialog(detail, item.Title);
            }
            else
            {
                this.ShowMessage(Resources.MsgHistoryNotFound);
            }
        }

        public ICommand OpenWebSiteCommand { get { return CreateCommand(OpenWebSite); } }
        private void OpenWebSite(object arg)
        {
            var treeItem = SelectedFolder;
            BlobDataManager.Instance.OpenWebSite(treeItem.Path);
        }

        public ICommand ShowUrlCommand { get { return CreateCommand((x)=>ShowUrl(SelectedFile)); } }
        private void ShowUrl(TreeItem treeItem)
        {
            if (treeItem == null) return;
            var vm = new ShowUrlVM();
            vm.Url = treeItem.Url;
            vm.FontSize = this.FontSize;
            ShowDialog(vm);
        }

        public ICommand CopyUrlToClipboardCommand { get { return CreateCommand((x) => CopyUrlToClipboard(SelectedFile)); } }
        private void CopyUrlToClipboard(TreeItem treeItem)
        {
            Clipboard.SetText(treeItem.Url);
        }


        public ICommand FilesOpenCommand { 
            get { 
                return CreateCommand((x) => { ExecuteActionAsync(ExecuteFile(SelectedFile, false)); });
            }
        }

        [ExcludeFromCodeCoverage]
        public ICommand FilesSaveCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    var folder = ShowFolderDailog();
                    if (folder != null)
                    {

                        ExecuteActionAsync(FilesSaveAsync(folder, Files.Where(y => y.IsSelected)));
                    }
                });
            }
        }


        private async Task FilesSaveAsync(string targetPath, IEnumerable<TreeItem> list)
        {
            await DownloadFiles(targetPath, await ConvertFileItem(list.Select(x => x.Path)));
        }

        public ICommand FilesCopyCommand
        {
            get
            {
                return CreateCommand((x) => {
                    var dataObject = CreateFilesDataObject();
                    Clipboard.SetDataObject(dataObject);
                });
            }
        }

        public ICommand FilesCutCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    var dataObject = CreateFilesDataObject(true);
                    Clipboard.SetDataObject(dataObject, false);
                });
            }
        }

        public ICommand FilesPasteCommand
        {
            get { return CreateCommand((x) =>{ ExecuteActionAsync(PasteFiles(x), multiAction: true); }); }
        }
        private async Task PasteFiles(object e)
        {
            if (BlobDataManager.Instance.Offline) throw new ApplicationException(Properties.Resources.MsgCanNotWithOffline);

            IDataObject dataObj = Clipboard.GetDataObject() as IDataObject;
            if (dataObj != null)
            {
                bool cut = false;
                MemoryStream stream = (MemoryStream)dataObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_PREFERREDDROPEFFECT, true);
                if (stream != null)
                {
                    int flag = stream.ReadByte();
                    if (!(flag != 2 && flag != 5))
                    {
                        cut = (flag == 2);
                    }
                }

                string[] files = dataObj.GetData(DataFormats.FileDrop) as string[];
                if (files != null)
                {
                    var option = new Dictionary<string, object>();
                    option.Add("Confirm", true);
                    await UploadAsync(new FileCommandArg { Path = this.SelectedFolder.Path, Files = files, Data=option });
                }
                else if (dataObj.GetDataPresent(DataFormats.Serializable))
                {
                    files = GetDataObjectAsPaths(dataObj);
                    var arg = new FileCommandArg { Path = this.SelectedFolder.Path, Files = files };
                    await CopyOrMoveFile(arg, cut);
                }
                else if (dataObj.GetDataPresent(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILEDESCRIPTORW))
                {
                    var fileObj = new OutlookDataObject(dataObj);

                    string[] filenames = (string[])fileObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILEDESCRIPTORW);
                    MemoryStream[] filestreams = (MemoryStream[])fileObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILECONTENTS);

                    for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
                    {
                        string filename = filenames[fileIndex];
                        if (files != null && files.Contains(filename)) continue;

                        var uploadInfo = await BlobDataManager.Instance.UploadFileAsync(SelectedFolder.Path, filename, filestreams[fileIndex], DateTime.UtcNow, DateTime.UtcNow);
                        var f = await BlobDataManager.Instance.GetFileAsync(uploadInfo.UploadedPath);
                        UpdatePanel((FileItem)f);
                    }
                }
                else
                {
                    throw new NotSupportedException();
                }

            }
        }


        public ICommand FilesDeleteCommand
        {
            get { return CreateCommand((x) => { ExecuteActionAsync(FilesDelete(x), multiAction: true); }); }
        }
        private async Task FilesDelete(object e)
        {
            if (Files.Where(x=>x.IsSelected).Count() == 0) return;

            var title = Files.Where(x => x.IsSelected).Count() > 1 ? string.Format(Properties.Resources.MsgFilesTitle, Files.Where(x => x.IsSelected).Count()) : Files.Where(x => x.IsSelected).First().Title;

            if (this.ShowOKCancel(string.Format(Properties.Resources.MsgDeleteConfirm, title)))
            {
                var data = new FileCommandArg();
                data.Files = Files.Where(x => x.IsSelected).Cast<TreeItem>()
                    .Select(x => x.Path).ToArray();
                await DeleteFileAsync(data);
            }
        }

        public ICommand FilesDeleteCompletelyCommand
        {
            get { return CreateCommand((x) => { ExecuteActionAsync(FilesDeleteCompletely(x), multiAction: true); }); }
        }
        private async Task FilesDeleteCompletely(object e)
        {
            if (Files.Where(x => x.IsSelected).Count() == 0) return;

            var title = Files.Where(x => x.IsSelected).Count() > 1 ? string.Format(Properties.Resources.MsgFilesTitle, Files.Where(x => x.IsSelected).Count()) : Files.Where(x => x.IsSelected).First().Title;

            if (this.ShowOKCancel(string.Format(Properties.Resources.MsgConfirmCompetelyDelete, title)))
            {
                var data = new FileCommandArg();
                data.Files = Files.Where(x => x.IsSelected).Cast<TreeItem>()
                    .Select(x => x.Path).ToArray();
                data.Data["Completely"] = true;
                await DeleteFileAsync(data);
            }
        }

        public ICommand FolderDeleteCommand
        {
            get 
            { 
                return CreateCommand((x) => {
                    var path = SelectedFolder.Path;
                    var title = SelectedFolder.Title;
                    if (this.ShowOKCancel(string.Format(Properties.Resources.MsgDeleteConfirm, title)))
                    {
                        var data = new FileCommandArg();
                        data.Files = new string[] { path };
                        ExecuteActionAsync(DeleteFileAsync(data), multiAction: true);
                    }
                }); 
            }
        }


        public ICommand FolderCopyCommand
        {
            get
            {
                return CreateCommand((x) => {
                    var dataObject = CreateFolderDataObject();
                    Clipboard.SetDataObject(dataObject);
                });
            }
        }

        private System.Runtime.InteropServices.ComTypes.IDataObject CreateFolderDataObject(bool cut = false)
        {
            var dataObject = new VirtualFileDataObject();
            SetPathToDataObject(dataObject, new string[] { SelectedFolder.Path }, cut);
            return dataObject;
        }

        public ICommand FolderCutCommand
        {
            get
            {
                return CreateCommand((x) =>
                {
                    var dataObject = CreateFolderDataObject(true);
                    Clipboard.SetDataObject(dataObject);
                });
            }
        }


        public ICommand FolderPasteCommand
        {
            get { return CreateCommand((x) => { ExecuteActionAsync(PasteFolder(SelectedFolder), multiAction: true); }); }
        }


        private async Task PasteFolder(TreeItem item)
        {
            if (BlobDataManager.Instance.Offline) throw new ApplicationException(Properties.Resources.MsgCanNotWithOffline);

            IDataObject dataObj = Clipboard.GetDataObject() as IDataObject;
            if (dataObj != null)
            {
                bool cut = false;
                MemoryStream stream = (MemoryStream)dataObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_PREFERREDDROPEFFECT, true);
                if (stream != null)
                {
                    int flag = stream.ReadByte();
                    if (!(flag != 2 && flag != 5))
                    {
                        cut = (flag == 2);
                    }
                }

                string[] files = dataObj.GetData(DataFormats.FileDrop) as string[];
                if (files != null)
                {
                    var arg = new FileCommandArg { Path = item.Path, Files = files };
                    await UploadAsync(arg);
                }
                else if (dataObj.GetDataPresent(DataFormats.Serializable))
                {
                    files = GetDataObjectAsPaths(dataObj);
                    var arg = new FileCommandArg { Path = item.Path, Files = files };
                    await CopyOrMoveFile(arg, cut);
                }
                else if (dataObj.GetDataPresent(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILEDESCRIPTORW))
                {
                    var fileObj = new OutlookDataObject(dataObj);

                    string[] filenames = (string[])fileObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILEDESCRIPTORW);
                    MemoryStream[] filestreams = (MemoryStream[])fileObj.GetData(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_FILECONTENTS);

                    for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
                    {
                        string filename = filenames[fileIndex];
                        if (files != null && files.Contains(filename)) continue;
                        var uploadInfo = await BlobDataManager.Instance.UploadFileAsync(item.Path, filename, filestreams[fileIndex], DateTime.UtcNow, DateTime.UtcNow);
                        var f = await BlobDataManager.Instance.GetFileAsync(uploadInfo.UploadedPath);
                        UpdatePanel((FileItem)f);
                    }
                }
                else
                {
                    throw new NotSupportedException();
                }

            }
        }

        public string[] GetDataObjectAsPaths(IDataObject dataObj)
        {
            var st = dataObj.GetData(DataFormats.Serializable) as MemoryStream;
            if (st != null)
            {
                BinaryFormatter bin = new BinaryFormatter();
                var files = (string[])bin.Deserialize(st);
                return files;
            }
            return null;
        }

        public MainVM()
        {
            Folders = new ObservableCollection<DirectoryItem>();
            Files = new ObservableCollection<FileItem>();
            BlobDataManager.Instance.ItemNotified += Instance_ItemNotified;
            BlobDataManager.Instance.RecentChanged += Instance_RecentChanged;
            StartEditEnd();
        }

 
        public System.Runtime.InteropServices.ComTypes.IDataObject CreateFilesDataObject(bool cut = false)
        {
            var virtualFileDataObject = new VirtualFileDataObject();
            var downloadList = new Dictionary<string, double>();


            var SelectedItems = Files.Where(x => x.IsSelected).Cast<FileItem>()
            .Select(x => new VirtualFileDataObject.FileDescriptor()
            {
                Name = x.AttachmentFileName,
                ChangeTimeUtc = x.LastModified,
                //Length = x.Length, ZIPやパスワードをするためサイズは不明
                StreamContents = stream =>
                {

                    try
                    {
                        CanCanceled = true;
                        IsCancelled = false;
                        IsBusy = true;
                        ProgressMessage = Resources.MsgProcessing;
                        Message = "";

                        var progress = new Progress<double>();
                        progress.ProgressChanged += (s, e) =>
                        {
                            ProgressMessage = string.Format(Resources.MsgDownloadFile, System.IO.Path.GetFileName(x.Path)) + string.Format(" {0:0}%", e);
                        };

                        DoEvents();

                        var cache = BlobDataManager.Instance.GetCachedFile(x) ?? BlobDataManager.Instance.CreateCacheFileEntry(x);
                        
                        Task task;

                        if (cache != null && cache.IsDownloaded)
                        {
                            task = Task.Run(() => {
                                using (var st = new FileStream(cache.LocalPath, FileMode.Open, FileAccess.Read))
                                {
                                    st.CopyTo(stream);
                                }
                                cache.LastAccessTime = DateTime.Now;
                            });
                        }
                        else
                        {
                            task = BlobDataManager.Instance.DownloadAsync(x.Path, stream, null, x.DownloadMode, progress, x.Length);
                            cache.LastAccessTime = DateTime.Now;
                        }
                    
                        while (!task.IsCompleted)
                        {
                            DoEvents();
                        }
                        if (task.IsFaulted)
                        {
                            if (task.Exception.InnerException != null)
                            {
                                Message = task.Exception.InnerException.Message;
                            }
                            else
                            {
                                Message = task.Exception.Message;
                            }

                            this.ShowMessage(Message);
                        }
                        else
                        {
                            ProgressMessage = "";
                        }
                        IsBusy = false;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.ToString());
                        if (ex.InnerException != null)
                        {
                            Message = ex.InnerException.Message;
                        }
                        else
                        {
                            Message = ex.Message;
                        }
                        this.ShowMessage(Message);
                    }
                    finally
                    {
                        IsBusy = false;

                    }
                }
            })
            .ToArray();

            virtualFileDataObject.SetData(SelectedItems);


            var pathslist = Files.Where(x => x.IsSelected && !x.IsLocalOnly).Cast<TreeItem>().Select(x=>x.Path).ToArray();
            //var pathsContent = pathslist.SelectMany(x => Encoding.Unicode.GetBytes(x.Path + "\0"));

            if (pathslist.Length > 0) SetPathToDataObject(virtualFileDataObject, pathslist, cut);

            return virtualFileDataObject;
        }

        public void SetPathToDataObject(VirtualFileDataObject virtualFileDataObject, string[] pathslist,bool cut)
        {
            MemoryStream memo = new MemoryStream(4);
            byte[] bytes = new byte[] { (byte)(cut ? 2 : 5), 0, 0, 0 };
            memo.Write(bytes, 0, bytes.Length);
            virtualFileDataObject.SetData((short)(DataFormats.GetDataFormat(CloudStorageLight.WpfClient.VirtualFileDataObject.NativeMethods.CFSTR_PREFERREDDROPEFFECT).Id), memo.ToArray());
            using (MemoryStream mem = new MemoryStream())
            {
                BinaryFormatter bin = new BinaryFormatter();
                bin.Serialize(mem, pathslist);
                var paths = (short)(DataFormats.GetDataFormat(DataFormats.Serializable).Id);
                virtualFileDataObject.SetData(paths, mem.ToArray());
            }
        }


        void Instance_ItemNotified(object sender, BlobDataManagerArgs e)
        {
            UpdateRemark(e);
        }

        void Instance_RecentChanged(object sender, EventArgs e)
        {
            ExecuteUIProc(() =>
            {
                OnPropertyChanged("RecentFiles","EditingFiles");
            });
        }

        [ExcludeFromCodeCoverage]
        private void UpdateRemark(BlobDataManagerArgs e)
        {
            var listItem = Files.Where(x => x.Path == e.Path).FirstOrDefault();
            if (listItem != null)
            {
                if (e.Message != null) listItem.Message = e.Message;
                if (e.CheckoutUser != null) listItem.CheckoutUser = e.CheckoutUser;
            }
        }

        //private void UpdateRemark()
        //{
        //    foreach (var file in BlobDataManager.Instance.GetErroredCachedFile())
        //    {
        //        var listItem = Files.Where(x => x.Path == file.Path).FirstOrDefault();
        //        if (listItem != null) listItem.Remark = file.ErrorMessage;
        //    }
        //}


        public void Init()
        {

            Folders.Clear();
            Files.Clear();
            var root = new DirectoryItem()
            {
                Path = "/",
                Title = BlobDataManager.Instance.Offline ? "Home(Offline)" : "Home",
                Type = "Directory",
            };
            //var favoite = new FavoriteDirectoryItem()
            //{
            //    Path = "",
            //    Title = Properties.Resources.MsgFavorites,
            //};
            //foreach (var f in BlobDataManager.Instance.LoadFavorites())
            //{
            //    var fd = new FavoriteItem()
            //    {
            //        Path = f,
            //        Title = f,
            //        Type = "Directory",
            //    };
            //    favoite.Folders.Add(fd);
            //}
            //Folders.Add(favoite);
            Folders.Add(root);
            SelectedFolder = root;

            var findPath = StartPath == "/" ? "Home" : StartPath;
            ExecuteActionAsync(LoadStartPathAsync(Folders[0], StartPath), 
                (x) => ShowPath(Folders[0], StartPath), string.Format(Resources.MsgSearchFiles, findPath));
            
            OnPropertyChanged(null);
        }

        public ICommand LoadStartPathCommand
        {
            get
            {
                return CreateCommand((targetPath) => {
                    ExecuteActionAsync(LoadStartPathAsync(Folders[0], (string)targetPath), 
                        (x) => ShowPath(Folders[0], (string)targetPath), string.Format(Resources.MsgSearchFiles, targetPath));
                });
            }
        }

        private async Task LoadStartPathAsync(DirectoryItem current, string targetPath)
        {
            await LoadChildrenAsync(current,false);
            if (targetPath == null) return;
            foreach (var dire in current.Folders)
            {
                if (targetPath.StartsWith(dire.Path))
                {
                    await LoadStartPathAsync(dire, targetPath);
                }
            }
        }

        private void ShowPath(DirectoryItem current, string targetPath)
        {
            SelectedFolder = current;
            SelectedFolder.IsSelected = true;
            SelectedFolder.IsExpanded = true;
            
            foreach (var dire in current.Folders)
            {
                if (targetPath != null && targetPath.StartsWith(dire.Path))
                {
                    ShowPath(dire, targetPath);
                }
            }
        }


        private async Task LoadChildrenAsync(DirectoryItem item, bool force)
        {
            await item.LoadChildrenAsync(force);
        }





        private async Task SetSelectedFolderAsync(DirectoryItem value, bool refresh = false)
        {
            if (stockHistory && _SelectedFolder != null) EntryHistory(_SelectedFolder.Path);
            _SelectedFolder = value;
            _SelectedFolder.IsSelected = true;
            Files.Clear();
            await SelectedFolder.LoadChildrenAsync(refresh);

            foreach (var item in SelectedFolder.Files)
            {
                Files.Add(item);
            }

            //UpdateRemark(); 
            OnPropertyChanged("SelectedFolder", "Files", "CurrentPath");
        }



        internal async Task ChangeCurrentPath(string value)
        {
            if (SelectedFolder == null || SelectedFolder.Path != value)
            {
                var item = await FindDirectoryAsync(value);
                if (item == null && value.StartsWith("http"))
                {
                    value = HttpUtility.UrlDecode(value);
                    var svrUrl = BlobDataManager.Instance.GetFilePathUrl("");
                    value = value.Substring(svrUrl.Length);
                    item = await FindDirectoryAsync(value);
                }
                if (item != null)
                {
                    if (stockHistory)
                    {
                        EntryHistory(SelectedFolder.Path);
                    }
                    await SetSelectedFolderAsync(item);
                    item.IsSelected = true;
                }
            }
        }

        private void EntryHistory(string value)
        {
            history = history.Take(histroyIndex).ToList();
            if (histroyIndex == 0 || history[histroyIndex - 1] != value)
            {
                history.Add(value);
                histroyIndex++;
            }
        }

        private string BackHistory()
        {
            if (histroyIndex <= 1) return null;
            histroyIndex--;
            return history[histroyIndex-1];
        }

        private string ForwardHistory()
        {
            if (histroyIndex >= history.Count) return null;
            histroyIndex++;
            return history[histroyIndex-1];
        }


        [ExcludeFromCodeCoverage]
        protected virtual void ShowDialog(ViewModelBase vm, string title = null)
        {
            var view = (WpfWindowView)ViewUtil.BuildView(vm, false);
            if (title != null) view.Window.Title = title;
            view.Window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            var active = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive); ;
            if (active != null) view.Window.Owner = active;
            view.ShowDialog();
        }

        #region private utility

        private async Task<DirectoryItem> FindDirectoryAsync(string value)
        {
            return await FindDirectoryAsync(value.TrimEnd('/'), Folders[0]);
        }

        private async Task<DirectoryItem> FindDirectoryAsync(string value, DirectoryItem target)
        {
            if (target == null) return null;
            if (string.Compare(target.Path.TrimEnd('/'), value, StringComparison.InvariantCultureIgnoreCase) == 0) return target;
            if (value.StartsWith(target.Path, StringComparison.InvariantCultureIgnoreCase))
            {
                await target.LoadChildrenAsync();
                foreach (var child in target.Folders)
                {
                    if (!value.StartsWith(child.Path.TrimEnd('/'))) continue;
                    var ret = await FindDirectoryAsync(value, child);
                    if (ret != null) return ret;
                }
            }
            return null;
        }

        private TreeItem FindItem(string path)
        {
            return FindItem(Folders, path);
        }

        private TreeItem FindItem(IEnumerable<TreeItem> cont, string path)
        {
            if (cont != null)
            {
                foreach (var child in cont)
                {
                    if (child.Path == path) return child;
                    var dire = child as DirectoryItem;
                    if (dire != null)
                    {
                        var ret = FindItem(dire.Files, path);
                        if (ret != null) return ret;
                        ret = FindItem(dire.Folders, path);
                        if (ret != null) return ret;
                    }
                }
            }
            return null;
        }

        private void RemoveItem(TreeItem item)
        {
            RemoveItem(Folders[0], item);
        }

        private void RemoveItem(DirectoryItem cont, TreeItem item)
        {
            var file = this.Files.FirstOrDefault(x => x.Path == item.Path);
            if (file != null) this.Files.Remove(file);

            foreach (var child in cont.Folders)
            {
                if (IsCancelled) break;

                if (child.Path == item.Path)
                {
                    cont.Folders.Remove(child);
                    return;
                }
                RemoveItem(child, item);
            }

            foreach (var child in cont.Files)
            {
                if (IsCancelled) break;

                if (child.Path == item.Path)
                {
                    cont.Files.Remove(child);
                    return;
                }
            }
        }

        //private DirectoryItem GetParent(string path)
        //{
        //    return GetParent(Folders[0], path);
        //}
        //private DirectoryItem GetParent(DirectoryItem cont, string path)
        //{
        //    var file = cont.Files.FirstOrDefault(x => x.Path == path);
        //    if (file != null) return cont;

        //    foreach (var child in cont.Folders)
        //    {
        //        if (child.Path == path)
        //        {
        //            return cont;
        //        }
        //        var ret = GetParent(child, path);
        //        if (ret != null) return ret;
        //    }
        //    return null;
        //}

        private string GetFileName(string path)
        {
            var items = path.Split('/');
            return items.Last();
        }

        private string GetDirectoryName(string path)
        {
            path = GetDirectoryPath(path);
            if (path == "/") return path;
            
            var items = path.Trim('/').Split('/');
            return items.Last();
        }

        private string GetDirectoryPath(string path)
        {
            if (path.EndsWith("/")) return path;
            var items = path.Split('/');
            return items.Length > 1 ? path.Substring(0, path.Length-items.Last().Length) : "/";
        }

        internal string GetParentDirectoryPath(string path)
        {
            if (path.EndsWith("/")) return GetDirectoryPath(path.TrimEnd('/'));
            return GetDirectoryPath(path);
        }

        #endregion

        public void StartEditEnd()
        {
            if (Application.Current != null)
            {
                var timer = new DispatcherTimer
                    (
                    TimeSpan.FromSeconds(1),
                    DispatcherPriority.ApplicationIdle,
                    (s, e) => { IdleDoWork(); },
                    Application.Current.Dispatcher
                    );
            }
        }

        class LockWorkInfo
        {
            public string Path { get; set; }
            public DateTime? LastModifiedDate { get; set; }
        }
        private List<LockWorkInfo> lockInfoList;

        void IdleDoWork()
        {
            if (lockInfoList == null)
                lockInfoList = new List<LockWorkInfo>();

            foreach (var lockFile in BlobDataManager.Instance.GetEditingFiles())
            {
                try
                {
                    var lockMark = lockInfoList.Where(x => x.Path == lockFile.Path).FirstOrDefault();
                    if (lockMark == null)
                    {
                        if (RestartManager.IsProcessesUsingFile(lockFile.LocalPath))
                        {
                            lockInfoList.Add(new LockWorkInfo { Path = lockFile.Path, LastModifiedDate = File.GetLastWriteTime(lockFile.LocalPath) });
                        }
                    }
                    else
                    {
                        if (!RestartManager.IsProcessesUsingFile(lockFile.LocalPath))
                        {
                            if (IsBusy) break;
                            var result = this.ShowOKCancel(string.Format(Properties.Resources.MsgConfirmCheckin, System.IO.Path.GetFileName(lockFile.LocalPath)));
                            if (result)
                            {
                                var fileItem = FindItem(lockFile.Path);
                                if (fileItem == null) fileItem = new FileItem { Path = lockFile.Path };
                                CheckinCommand.Execute(fileItem);
                            }
                            lockInfoList.Remove(lockMark);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }
        }


        [ExcludeFromCodeCoverage]
        public ICommand LoadTestCommand 
        { 
            get 
            {
                return CreateCommand((x) => { ExecuteActionAsync(LoadTest()); });
            } 
        }
        [ExcludeFromCodeCoverage]
        private async Task LoadTest()
        {
            System.Net.ServicePointManager.DefaultConnectionLimit = 1000;
            try
            {
                var files = await BlobDataManager.Instance.GetFilesAsync(SelectedFolder.Path, 0);
                for (int i = 5; i < 1000; i *= 2)
                {
                    var sw = Stopwatch.StartNew();
                    if (IsCancelled) break;
                    var taskList = new List<Task>();
                    var filelist = new List<string>();
                    for (int j=0; j<i*2; j++)
                    {
                        if (IsCancelled) break;
                        var temp = System.IO.Path.GetTempFileName();
                        try
                        {
                            filelist.Add(temp);
                            taskList.Add(BlobDataManager.Instance.DownloadAsync(files[j % files.Count].Path, temp, null, null, null));

                        }
                        catch(Exception ex)
                        {
                            Trace.WriteLine(ex);
                        }

                    }
                    await Task.WhenAll(taskList.ToArray());
                    foreach (var temp in filelist)
                    {
                        if (File.Exists(temp)) File.Delete(temp);
                    }
                    ProgressMessage = "LoadTest " + i.ToString() + " " + sw.Elapsed.ToString();
                    Trace.WriteLine(ProgressMessage);
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }
        }

        public ICommand AddFavoriteCommand
        {
            get { return CreateCommand((x)=> {
                var fList = FavoriteList;
                if (fList.Contains(this.SelectedFolder.Path)) return;
                fList.Add(this.SelectedFolder.Path);
                BlobDataManager.Instance.SaveFavorites(fList);
                OnPropertyChanged("FavoriteList");
            }); }
        }
        public ICommand RemoveFavoriteCommand
        {
            get
            {
                return CreateCommand((x) => {
                    var fList = FavoriteList;
                    if (!fList.Contains((string)x)) return;
                    fList.Remove((string)x);
                    BlobDataManager.Instance.SaveFavorites(fList);
                    OnPropertyChanged("FavoriteList");
                });
            }
        }
    }
}
